# Copyright (c) 2022 Presto Labs Pte. Ltd.
# Author: lucasyoo

from cc.appcoin2.strategy.umm.support.util import *
import dateutil.parser

USE_CONFIG_SERVICE = False
LOG_PRIVATE_MESSAGE = False
STORE_PARSED_MESSAGE = False


if __name__ == "__main__":
  try:
    ctrl_flag = int(sys.argv[3])
  except Exception:
    ctrl_flag = 0

  ctrl_flag = '{0:08b}'.format(ctrl_flag)
  ctrl_flag = ctrl_flag[::-1]

  if ctrl_flag[0] == '1':
    LOG_PRIVATE_MESSAGE = True

  if ctrl_flag[1] == '1':
    USE_CONFIG_SERVICE = True


class StratConfigHamm(StratConfig):
  def __init__(
      self,
      *,
      exchange=None,
      tag=None,
      is_sim=False,
      config_path=None,
      interval=None,
      sim_dir=None,
      analyzer_feed_machine=None):
    self.is_sim = is_sim
    self.sim_dir = sim_dir
    self.interval = interval
    self.analyzer_feed_machine = analyzer_feed_machine

    # converter
    self.app_config = {
        'feed': {'exchanges': {}},
        'order': {'exchanges': {}},
        'log': None,
        'strategy': None,
    }

    if self.is_sim:
      config_path = pathlib.Path(config_path)
    else:
      config_path = get_git_root().joinpath(
          'python', 'coin_deploy', 'hamm', 'config',
          exchange, f'hamm_config_{tag}.json')

    self.config = json.loads(config_path.read_text())
    self.config_path = config_path.as_posix()

  def add_feed_product(self, uniq_product):
    mea = self.get_mea(uniq_product)
    if 'okex' in mea.lower() and 'v5' in mea.lower():
      mea = mea.replace('v5', 'v3')

    self.init_feed(mea)
    norm_prod = self.feed["exchanges"][mea]["products"]["norms"]
    norm_prod.append(get_norm_product(uniq_product))

  def add_order_product(self, uniq_product):
    mea = self.get_mea(uniq_product)
    self.init_order(mea)
    exch = mea.split('.')[1]
    norm_prod = self.order["exchanges"][exch]["products"]["norms"]
    nprod = get_norm_product(uniq_product)
    norm_prod.append(nprod)

    if self.is_sim:
      account = self.order['exchanges'][exch]['sim_config2']['accounts']
      account.append(self.gen_sim_config_account(uniq_product))

  def get_mea(self, uniq_product):
    market, exchange, product = uniq_product.split(':', 2)
    me = f'{market}.{exchange}'

    if me == 'Spot.Binance':
      return me + '.v1'

    if me == 'Futures.Binance':
      if 'USDT' in product:
        return me + '.v1'
      else:
        return me + '.v1-delivery'

    if me == 'Spot.Ftx':
      return me + '.v1'

    if me == 'Futures.Ftx':
      return me + '.v1'

    if me == 'Spot.Gateio':
      return me + '.v4'

    if me == 'Spot.Huobi':
      return me + '.v1'

    if me == 'Spot.Mexc':
      return me + '.v2'

    if me == 'Spot.Kucoin':
      return me + '.v1'

    # If not found, raise exception.
    raise(Exception('Unknown product: ' + uniq_product))


  def get_reserve(self, uniq_product):
    conf_pricer = self.config['subconfig'][uniq_product]['pass_executor_config']
    if 'reserve' in conf_pricer:
      return conf_pricer['reserve']

  def get_pos_range(self, uniq_product):
    conf_exe = self.config['subconfig'][uniq_product]['pass_executor_config']
    if 'min_pos' in conf_exe:
      return conf_exe['min_pos'], conf_exe['max_pos']

  def is_valid_key(self, sub, key):
    valid_keys = {
        'common': {
          'arb_tolerance_bps',
          'randomize_order',
          'order_update_period_sec',
          'max_posting_period_sec',
          'fill_cooldown_period_sec',
        },
        'pricer': {
            'is_closing_time_infinite', 'vol_window_sec', 'vol_tick_intv_sec', 'init_vol',
            'maker_fee_bps', 'min_spread_bps', 'max_spread_bps', 'target_ratio', 
            'order_qty_generator_config', 'trade_window_sec', 'price_type', 'spread_control',
            'trade_cnt', "q_method", "lean_method", "init_vol_time_sec", "lean_risk_averse",
            "vol_risk_averse", "max_lean_bps", "lot_size_quote", "fade_bps_multiplier",
            "fade_decay_sec", "fill_num_hurdle", "fill_window_sec", "fill_price_limit_sec",
            "ewma_alpha", "min_lot_size_quote", "min_bandwidth_bp", "max_bandwidth_bp",
            "layering_num"
        },
        'order_qty_generator_config': {
            'eta', 'order_qty_mode', 'max_spread_bps', 'is_order_decay', 'book_level'
        },
        'exe': {
            'lot_size', 'max_pos', 'min_pos', 'sticky_bp', 'reserve'
        },
        'agg_controller': {
            'agg_cooldown_period_sec',
            'agg_order_update_period_sec',
            'agg_sw_quote_thold',
            'agg_lw_quote_thold',
            'agg_sw_fill_sec',
            'agg_lw_fill_sec',
            'agg_qty_thold',
            'agg_update_intv_sec',
            'agg_midp_ret_thold_bp',
            'agg_vol_thold',
            'agg_midp_window_sec',
            'agg_vol_window_sec',
            'agg_skip_qty_thold',
            'agg_skip_quote_thold',
            'agg_max_consecutive_skip',
            'agg_pricing_diff_thold'
        }
    }
    return key in valid_keys[sub]

  def update_params(self, params):
    for key, value in params.items():
      if value is None:
        continue
      
      if self.is_valid_key('common', key):
        self.config[key] = value

      if self.is_valid_key('pricer', key):
        for config in self.config['subconfig'].values():
          config['pricer_config'][key] = value

      elif self.is_valid_key('exe', key):
        for config in self.config['subconfig'].values():
          config['pass_executor_config'][key] = value

      elif self.is_valid_key('order_qty_generator_config', key):
        if not isinstance(config['pricer_config']['order_qty_generator_config'], dict):
          config['pricer_config']['order_qty_generator_config'] = {}
        config['pricer_config']['order_qty_generator_config'][key] = value

      elif self.is_valid_key('agg_controller', key):
        self.config['agg_controller_config'][key.replace('agg_', '')] = value
      else:
        continue

  def init_pass_executor_config(self):
    for symbol, config in self.config['subconfig'].items():
      pass_exe_config = config['pass_executor_config']

      pass_exe_config['mea'] = self.get_mea(symbol)
      pass_exe_config['symbol'] = symbol.split(':')[2]

  def to_timestamp(self, date_str):
    return int(dateutil.parser.parse(date_str).timestamp() * 1e9)

  def gen_sim_config_account(self, uniq_product):
    min_pos, max_pos = self.get_pos_range(uniq_product)
    reserve_pos = self.get_reserve(uniq_product)
    if not reserve_pos:
      reserve_pos = (max_pos + min_pos) / 2.

    nprod = get_norm_product(uniq_product)
    start_time = self.to_timestamp(self.interval[0])
    end_time = self.to_timestamp(self.interval[1])
    sim_res_path = os.path.join(self.sim_dir, '.'.join(self.interval + [self.get_mea(uniq_product), nprod, "pb"]))
    config = {
        "sim_account_id": 0,
        "sim_result_filepath": sim_res_path,
        "relative_norm": nprod,
        "initial_position": 0,
        "reserve_position": reserve_pos,
        "is_snapshot": False,
        "latency_sec": 0.1,
        "cancel_latency_sec": 0,
        "dump_fill_only": False,
        "result_format": "RESULT_FORMAT_PROTO_FILE_DUMP",
        "start_timestamp": start_time,
        "end_timestamp": end_time,
        "use_order_latency_model": False,
        "makerfill_tolerance_bps": 0.0,
        "use_exchange_timestamp": True
    }
    return config

  def init_strategy(self):
    config = self.config
    strategy_name = config['strategy_name']
    strategy_group = config['strategy_group']
    self.app_config["strategy"] = {
        "strategy_group": strategy_group,
        "strategy_name": strategy_name,
    }

  # def init_pricer_name(self):
  #   for symbol, config in self.config['subconfig'].items():
  #     config['pricer_config']['name'] = symbol

  def convert_to_app_config(self):
    for symbol, _ in self.config['subconfig'].items():
      self.add_feed_product(symbol)
      self.add_order_product(symbol)

  def init_feed(self, mea):
    if 'common' not in self.feed:
      config = {}
      if self.is_sim:
        config["type"] = "FEED_SYSTEM_TYPE_ARCHIVE"
        arch_config = {
            'archive_type': 'FASTFEED',
            'machine': self.analyzer_feed_machine,
            'interval': {
                'start': self.interval[0],
                'end': self.interval[1],
            },
            'worker': '0',
        }
        config['archive'] = arch_config
      else:
        config["type"] = "FEED_SYSTEM_TYPE_LIVE"
        config["websocket"] = {"num_workers": 1}
      self.feed['common'] = config

    if mea in self.feed["exchanges"]:
      return

    config = {}
    config["mea"] = mea
    config["products"] = {"norms": []}
    config["recipe"] = self.get_recipe(mea)
    self.feed["exchanges"][mea] = config

  def get_recipe(self, mea_str):
    if mea_str == 'Spot.Lmaxdigital.v1':
      return 'realtime'

    config = toml.load(open('data/coin2/feed/subscribers.toml'))
    mea_str = mea_str.replace('.', '_').lower()

    # handle okex v3 and v5 feed
    if 'okex' in mea_str and 'v5' in mea_str:
      mea_str = mea_str.replace('v5', 'v3')

    if 'kucoin' in mea_str:
      return 'realtime'

    config = config[mea_str]
    return config['default_recipe']

  def init_order(self, mea):
    exch = mea.split('.')[1]
    if exch in self.order["exchanges"]:
      return

    config = {}
    config["mea"] = mea

    if self.is_sim:
      config["type"] = "ORDER_SYSTEM_TYPE_SIM_OG"
    else:
      config["type"] = "ORDER_SYSTEM_TYPE_LIVE_OG"

    config["products"] = {"norms": []}
    config["connection_config"] = {}
    config["order_logger_config"] = {"store_parsed_message": STORE_PARSED_MESSAGE}
    # config["order_gateway_config"] = {"log_private_exchange_message": LOG_PRIVATE_MESSAGE}

    if os.getenv('PREX_SANDBOX'):
      config['connection_config'].update({
          "rest_host": "api.sandbox.prex.sh",
          "ws_host": "api.sandbox.prex.sh",
      })

    if os.getenv('PREX_DEV'):
      config['connection_config'].update({
          "rest_host": "api.dev.pc.prex.sh",
          "ws_host": "api.dev.pc.prex.sh",
      })

    if self.is_sim:
      config['sim_config2'] = {'accounts': []}

    self.order["exchanges"][exch] = config

  def gen_config(self):
    self.init_log()
    self.init_strategy()
    self.init_pass_executor_config()
    self.convert_to_app_config()

def main(argv):
  sconfig = StratConfigHamm(exchange=argv[1], tag=argv[2])
  sconfig.gen_config()
  print(sconfig.run_script)


if __name__ == '__main__':
  sys.exit(main(sys.argv))